home *** CD-ROM | disk | FTP | other *** search
/ Chip 2007 January, February, March & April / Chip-Cover-CD-2007-02.iso / Pakiet bezpieczenstwa / mini Pentoo LiveCD 2006.1 / mpentoo-2006.1.iso / livecd.squashfs / usr / bin / intltool-merge < prev    next >
Text File  |  2005-10-18  |  35KB  |  1,357 lines

  1. #!/usr/bin/perl -w
  2. # -*- Mode: perl; indent-tabs-mode: nil; c-basic-offset: 4  -*-
  3.  
  4. #
  5. #  The Intltool Message Merger
  6. #
  7. #  Copyright (C) 2000, 2003 Free Software Foundation.
  8. #  Copyright (C) 2000, 2001 Eazel, Inc
  9. #
  10. #  Intltool is free software; you can redistribute it and/or
  11. #  modify it under the terms of the GNU General Public License 
  12. #  version 2 published by the Free Software Foundation.
  13. #
  14. #  Intltool is distributed in the hope that it will be useful,
  15. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  17. #  General Public License for more details.
  18. #
  19. #  You should have received a copy of the GNU General Public License
  20. #  along with this program; if not, write to the Free Software
  21. #  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  22. #
  23. #  As a special exception to the GNU General Public License, if you
  24. #  distribute this file as part of a program that contains a
  25. #  configuration script generated by Autoconf, you may include it under
  26. #  the same distribution terms that you use for the rest of that program.
  27. #
  28. #  Authors:  Maciej Stachowiak <mjs@noisehavoc.org>
  29. #            Kenneth Christiansen <kenneth@gnu.org>
  30. #            Darin Adler <darin@bentspoon.com>
  31. #
  32. #  Proper XML UTF-8'ification written by Cyrille Chepelov <chepelov@calixo.net>
  33. #
  34.  
  35. ## Release information
  36. my $PROGRAM = "intltool-merge";
  37. my $PACKAGE = "intltool";
  38. my $VERSION = "0.34.1";
  39.  
  40. ## Loaded modules
  41. use strict; 
  42. use Getopt::Long;
  43. use Text::Wrap;
  44. use File::Basename;
  45.  
  46. my $must_end_tag      = -1;
  47. my $last_depth        = -1;
  48. my $translation_depth = -1;
  49. my @tag_stack = ();
  50. my @entered_tag = ();
  51. my @translation_strings = ();
  52. my $leading_space = "";
  53.  
  54. ## Scalars used by the option stuff
  55. my $HELP_ARG = 0;
  56. my $VERSION_ARG = 0;
  57. my $BA_STYLE_ARG = 0;
  58. my $XML_STYLE_ARG = 0;
  59. my $KEYS_STYLE_ARG = 0;
  60. my $DESKTOP_STYLE_ARG = 0;
  61. my $SCHEMAS_STYLE_ARG = 0;
  62. my $RFC822DEB_STYLE_ARG = 0;
  63. my $QUIET_ARG = 0;
  64. my $PASS_THROUGH_ARG = 0;
  65. my $UTF8_ARG = 0;
  66. my $MULTIPLE_OUTPUT = 0;
  67. my $cache_file;
  68.  
  69. ## Handle options
  70. GetOptions 
  71. (
  72.  "help" => \$HELP_ARG,
  73.  "version" => \$VERSION_ARG,
  74.  "quiet|q" => \$QUIET_ARG,
  75.  "oaf-style|o" => \$BA_STYLE_ARG, ## for compatibility
  76.  "ba-style|b" => \$BA_STYLE_ARG,
  77.  "xml-style|x" => \$XML_STYLE_ARG,
  78.  "keys-style|k" => \$KEYS_STYLE_ARG,
  79.  "desktop-style|d" => \$DESKTOP_STYLE_ARG,
  80.  "schemas-style|s" => \$SCHEMAS_STYLE_ARG,
  81.  "rfc822deb-style|r" => \$RFC822DEB_STYLE_ARG,
  82.  "pass-through|p" => \$PASS_THROUGH_ARG,
  83.  "utf8|u" => \$UTF8_ARG,
  84.  "multiple-output|m" => \$MULTIPLE_OUTPUT,
  85.  "cache|c=s" => \$cache_file
  86.  ) or &error;
  87.  
  88. my $PO_DIR;
  89. my $FILE;
  90. my $OUTFILE;
  91.  
  92. my %po_files_by_lang = ();
  93. my %translations = ();
  94. my $iconv = $ENV{"ICONV"} || $ENV{"INTLTOOL_ICONV"} || "/usr/bin/iconv";
  95. my $devnull = ($^O eq 'MSWin32' ? 'NUL:' : '/dev/null');
  96.  
  97. # Use this instead of \w for XML files to handle more possible characters.
  98. my $w = "[-A-Za-z0-9._:]";
  99.  
  100. # XML quoted string contents
  101. my $q = "[^\\\"]*";
  102.  
  103. ## Check for options. 
  104.  
  105. if ($VERSION_ARG) 
  106. {
  107.     &print_version;
  108. elsif ($HELP_ARG) 
  109. {
  110.     &print_help;
  111. elsif ($BA_STYLE_ARG && @ARGV > 2) 
  112. {
  113.     &utf8_sanity_check;
  114.     &preparation;
  115.     &print_message;
  116.     &ba_merge_translations;
  117.     &finalize;
  118. elsif ($XML_STYLE_ARG && @ARGV > 2) 
  119. {
  120.     &utf8_sanity_check;
  121.     &preparation;
  122.     &print_message;
  123.     &xml_merge_output;
  124.     &finalize;
  125. elsif ($KEYS_STYLE_ARG && @ARGV > 2) 
  126. {
  127.     &utf8_sanity_check;
  128.     &preparation;
  129.     &print_message;
  130.     &keys_merge_translations;
  131.     &finalize;
  132. elsif ($DESKTOP_STYLE_ARG && @ARGV > 2) 
  133. {
  134.     &utf8_sanity_check;
  135.     &preparation;
  136.     &print_message;
  137.     &desktop_merge_translations;
  138.     &finalize;
  139. elsif ($SCHEMAS_STYLE_ARG && @ARGV > 2) 
  140. {
  141.     &utf8_sanity_check;
  142.     &preparation;
  143.     &print_message;
  144.     &schemas_merge_translations;
  145.     &finalize;
  146. elsif ($RFC822DEB_STYLE_ARG && @ARGV > 2) 
  147. {
  148.     &preparation;
  149.     &print_message;
  150.     &rfc822deb_merge_translations;
  151.     &finalize;
  152. else 
  153. {
  154.     &print_help;
  155. }
  156.  
  157. exit;
  158.  
  159. ## Sub for printing release information
  160. sub print_version
  161. {
  162.     print <<_EOF_;
  163. ${PROGRAM} (${PACKAGE}) ${VERSION}
  164. Written by Maciej Stachowiak, Darin Adler and Kenneth Christiansen.
  165.  
  166. Copyright (C) 2000-2003 Free Software Foundation, Inc.
  167. Copyright (C) 2000-2001 Eazel, Inc.
  168. This is free software; see the source for copying conditions.  There is NO
  169. warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  170. _EOF_
  171.     exit;
  172. }
  173.  
  174. ## Sub for printing usage information
  175. sub print_help
  176. {
  177.     print <<_EOF_;
  178. Usage: ${PROGRAM} [OPTION]... PO_DIRECTORY FILENAME OUTPUT_FILE
  179. Generates an output file that includes some localized attributes from an
  180. untranslated source file.
  181.  
  182. Mandatory options: (exactly one must be specified)
  183.   -b, --ba-style         includes translations in the bonobo-activation style
  184.   -d, --desktop-style    includes translations in the desktop style
  185.   -k, --keys-style       includes translations in the keys style
  186.   -s, --schemas-style    includes translations in the schemas style
  187.   -r, --rfc822deb-style  includes translations in the RFC822 style
  188.   -x, --xml-style        includes translations in the standard xml style
  189.  
  190. Other options:
  191.   -u, --utf8             convert all strings to UTF-8 before merging 
  192.                          (default for everything except RFC822 style)
  193.   -p, --pass-through     deprecated, does nothing and issues a warning
  194.   -m, --multiple-output  output one localized file per locale, instead of 
  195.                      a single file containing all localized elements
  196.   -c, --cache=FILE       specify cache file name
  197.                          (usually \$top_builddir/po/.intltool-merge-cache)
  198.   -q, --quiet            suppress most messages
  199.       --help             display this help and exit
  200.       --version          output version information and exit
  201.  
  202. Report bugs to http://bugzilla.gnome.org/ (product name "$PACKAGE")
  203. or send email to <xml-i18n-tools\@gnome.org>.
  204. _EOF_
  205.     exit;
  206. }
  207.  
  208.  
  209. ## Sub for printing error messages
  210. sub print_error
  211. {
  212.     print STDERR "Try `${PROGRAM} --help' for more information.\n";
  213.     exit;
  214. }
  215.  
  216.  
  217. sub print_message 
  218. {
  219.     print "Merging translations into $OUTFILE.\n" unless $QUIET_ARG;
  220. }
  221.  
  222.  
  223. sub preparation 
  224. {
  225.     $PO_DIR = $ARGV[0];
  226.     $FILE = $ARGV[1];
  227.     $OUTFILE = $ARGV[2];
  228.  
  229.     &gather_po_files;
  230.     &get_translation_database;
  231. }
  232.  
  233. # General-purpose code for looking up translations in .po files
  234.  
  235. sub po_file2lang
  236. {
  237.     my ($tmp) = @_; 
  238.     $tmp =~ s/^.*\/(.*)\.po$/$1/; 
  239.     return $tmp; 
  240. }
  241.  
  242. sub gather_po_files
  243. {
  244.     for my $po_file (glob "$PO_DIR/*.po") {
  245.     $po_files_by_lang{po_file2lang($po_file)} = $po_file;
  246.     }
  247. }
  248.  
  249. sub get_local_charset
  250. {
  251.     my ($encoding) = @_;
  252.     my $alias_file = $ENV{"G_CHARSET_ALIAS"} || "/usr/lib/charset.alias";
  253.  
  254.     # seek character encoding aliases in charset.alias (glib)
  255.  
  256.     if (open CHARSET_ALIAS, $alias_file) 
  257.     {
  258.     while (<CHARSET_ALIAS>) 
  259.         {
  260.             next if /^\#/;
  261.             return $1 if (/^\s*([-._a-zA-Z0-9]+)\s+$encoding\b/i)
  262.         }
  263.  
  264.         close CHARSET_ALIAS;
  265.     }
  266.  
  267.     # if not found, return input string
  268.  
  269.     return $encoding;
  270. }
  271.  
  272. sub get_po_encoding
  273. {
  274.     my ($in_po_file) = @_;
  275.     my $encoding = "";
  276.  
  277.     open IN_PO_FILE, $in_po_file or die;
  278.     while (<IN_PO_FILE>) 
  279.     {
  280.         ## example: "Content-Type: text/plain; charset=ISO-8859-1\n"
  281.         if (/Content-Type\:.*charset=([-a-zA-Z0-9]+)\\n/) 
  282.         {
  283.             $encoding = $1; 
  284.             last;
  285.         }
  286.     }
  287.     close IN_PO_FILE;
  288.  
  289.     if (!$encoding) 
  290.     {
  291.         print STDERR "Warning: no encoding found in $in_po_file. Assuming ISO-8859-1\n" unless $QUIET_ARG;
  292.         $encoding = "ISO-8859-1";
  293.     }
  294.  
  295.     system ("$iconv -f $encoding -t UTF-8 <$devnull 2>$devnull");
  296.     if ($?) {
  297.     $encoding = get_local_charset($encoding);
  298.     }
  299.  
  300.     return $encoding
  301. }
  302.  
  303. sub utf8_sanity_check 
  304. {
  305.     print STDERR "Warning: option --pass-through has been removed.\n" if $PASS_THROUGH_ARG;
  306.     $UTF8_ARG = 1;
  307. }
  308.  
  309. sub get_translation_database
  310. {
  311.     if ($cache_file) {
  312.     &get_cached_translation_database;
  313.     } else {
  314.         &create_translation_database;
  315.     }
  316. }
  317.  
  318. sub get_newest_po_age
  319. {
  320.     my $newest_age;
  321.  
  322.     foreach my $file (values %po_files_by_lang) 
  323.     {
  324.     my $file_age = -M $file;
  325.     $newest_age = $file_age if !$newest_age || $file_age < $newest_age;
  326.     }
  327.  
  328.     $newest_age = 0 if !$newest_age;
  329.  
  330.     return $newest_age;
  331. }
  332.  
  333. sub create_cache
  334. {
  335.     print "Generating and caching the translation database\n" unless $QUIET_ARG;
  336.  
  337.     &create_translation_database;
  338.  
  339.     open CACHE, ">$cache_file" || die;
  340.     print CACHE join "\x01", %translations;
  341.     close CACHE;
  342. }
  343.  
  344. sub load_cache 
  345. {
  346.     print "Found cached translation database\n" unless $QUIET_ARG;
  347.  
  348.     my $contents;
  349.     open CACHE, "<$cache_file" || die;
  350.     {
  351.         local $/;
  352.         $contents = <CACHE>;
  353.     }
  354.     close CACHE;
  355.     %translations = split "\x01", $contents;
  356. }
  357.  
  358. sub get_cached_translation_database
  359. {
  360.     my $cache_file_age = -M $cache_file;
  361.     if (defined $cache_file_age) 
  362.     {
  363.         if ($cache_file_age <= &get_newest_po_age) 
  364.         {
  365.             &load_cache;
  366.             return;
  367.         }
  368.         print "Found too-old cached translation database\n" unless $QUIET_ARG;
  369.     }
  370.  
  371.     &create_cache;
  372. }
  373.  
  374. sub create_translation_database
  375. {
  376.     for my $lang (keys %po_files_by_lang) 
  377.     {
  378.         my $po_file = $po_files_by_lang{$lang};
  379.  
  380.         if ($UTF8_ARG) 
  381.         {
  382.             my $encoding = get_po_encoding ($po_file);
  383.  
  384.             if (lc $encoding eq "utf-8") 
  385.             {
  386.                 open PO_FILE, "<$po_file";    
  387.             } 
  388.             else 
  389.             {
  390.         print STDERR "WARNING: $po_file is not in UTF-8 but $encoding, converting...\n" unless $QUIET_ARG;;
  391.  
  392.                 open PO_FILE, "$iconv -f $encoding -t UTF-8 $po_file|";    
  393.             }
  394.         } 
  395.         else 
  396.         {
  397.             open PO_FILE, "<$po_file";    
  398.         }
  399.  
  400.     my $nextfuzzy = 0;
  401.     my $inmsgid = 0;
  402.     my $inmsgstr = 0;
  403.     my $msgid = "";
  404.     my $msgstr = "";
  405.  
  406.         while (<PO_FILE>) 
  407.         {
  408.         $nextfuzzy = 1 if /^#, fuzzy/;
  409.        
  410.         if (/^msgid "((\\.|[^\\])*)"/ ) 
  411.             {
  412.         $translations{$lang, $msgid} = $msgstr if $inmsgstr && $msgid && $msgstr;
  413.         $msgid = "";
  414.         $msgstr = "";
  415.  
  416.         if ($nextfuzzy) {
  417.             $inmsgid = 0;
  418.         } else {
  419.             $msgid = unescape_po_string($1);
  420.             $inmsgid = 1;
  421.         }
  422.         $inmsgstr = 0;
  423.         $nextfuzzy = 0;
  424.         }
  425.  
  426.         if (/^msgstr "((\\.|[^\\])*)"/) 
  427.             {
  428.             $msgstr = unescape_po_string($1);
  429.         $inmsgstr = 1;
  430.         $inmsgid = 0;
  431.         }
  432.  
  433.         if (/^"((\\.|[^\\])*)"/) 
  434.             {
  435.             $msgid .= unescape_po_string($1) if $inmsgid;
  436.             $msgstr .= unescape_po_string($1) if $inmsgstr;
  437.         }
  438.     }
  439.     $translations{$lang, $msgid} = $msgstr if $inmsgstr && $msgid && $msgstr;
  440.     }
  441. }
  442.  
  443. sub finalize
  444. {
  445. }
  446.  
  447. sub unescape_one_sequence
  448. {
  449.     my ($sequence) = @_;
  450.  
  451.     return "\\" if $sequence eq "\\\\";
  452.     return "\"" if $sequence eq "\\\"";
  453.     return "\n" if $sequence eq "\\n";
  454.     return "\r" if $sequence eq "\\r";
  455.     return "\t" if $sequence eq "\\t";
  456.     return "\b" if $sequence eq "\\b";
  457.     return "\f" if $sequence eq "\\f";
  458.     return "\a" if $sequence eq "\\a";
  459.     return chr(11) if $sequence eq "\\v"; # vertical tab, see ascii(7)
  460.  
  461.     return chr(hex($1)) if ($sequence =~ /\\x([0-9a-fA-F]{2})/);
  462.     return chr(oct($1)) if ($sequence =~ /\\([0-7]{3})/);
  463.  
  464.     # FIXME: Is \0 supported as well? Kenneth and Rodney don't want it, see bug #48489
  465.  
  466.     return $sequence;
  467. }
  468.  
  469. sub unescape_po_string
  470. {
  471.     my ($string) = @_;
  472.  
  473.     $string =~ s/(\\x[0-9a-fA-F]{2}|\\[0-7]{3}|\\.)/unescape_one_sequence($1)/eg;
  474.  
  475.     return $string;
  476. }
  477.  
  478. ## NOTE: deal with < - < but not > - >  because it seems its ok to have 
  479. ## > in the entity. For further info please look at #84738.
  480. sub entity_decode
  481. {
  482.     local ($_) = @_;
  483.  
  484.     s/'/'/g; # '
  485.     s/"/"/g; # "
  486.     s/&/&/g;
  487.     s/</</g;
  488.  
  489.     return $_;
  490. }
  491.  
  492. # entity_encode: (string)
  493. #
  494. # Encode the given string to XML format (encode '<' etc).
  495.  
  496. sub entity_encode
  497. {
  498.     my ($pre_encoded) = @_;
  499.  
  500.     my @list_of_chars = unpack ('C*', $pre_encoded);
  501.  
  502.     # with UTF-8 we only encode minimalistic
  503.     return join ('', map (&entity_encode_int_minimalist, @list_of_chars));
  504. }
  505.  
  506. sub entity_encode_int_minimalist
  507. {
  508.     return """ if $_ == 34;
  509.     return "&" if $_ == 38;
  510.     return "'" if $_ == 39;
  511.     return "<" if $_ == 60;
  512.     return chr $_;
  513. }
  514.  
  515. sub entity_encoded_translation
  516. {
  517.     my ($lang, $string) = @_;
  518.  
  519.     my $translation = $translations{$lang, $string};
  520.     return $string if !$translation;
  521.     return entity_encode ($translation);
  522. }
  523.  
  524. ## XML (bonobo-activation specific) merge code
  525.  
  526. sub ba_merge_translations
  527. {
  528.     my $source;
  529.  
  530.     {
  531.        local $/; # slurp mode
  532.        open INPUT, "<$FILE" or die "can't open $FILE: $!";
  533.        $source = <INPUT>;
  534.        close INPUT;
  535.     }
  536.  
  537.     open OUTPUT, ">$OUTFILE" or die "can't open $OUTFILE: $!";
  538.     # Binmode so that selftest works ok if using a native Win32 Perl...
  539.     binmode (OUTPUT) if $^O eq 'MSWin32';
  540.  
  541.     while ($source =~ s|^(.*?)([ \t]*<\s*$w+\s+($w+\s*=\s*"$q"\s*)+/?>)([ \t]*\n)?||s) 
  542.     {
  543.         print OUTPUT $1;
  544.  
  545.         my $node = $2 . "\n";
  546.  
  547.         my @strings = ();
  548.         $_ = $node;
  549.     while (s/(\s)_($w+\s*=\s*"($q)")/$1$2/s) {
  550.              push @strings, entity_decode($3);
  551.         }
  552.     print OUTPUT;
  553.  
  554.     my %langs;
  555.     for my $string (@strings) 
  556.         {
  557.         for my $lang (keys %po_files_by_lang) 
  558.             {
  559.                 $langs{$lang} = 1 if $translations{$lang, $string};
  560.         }
  561.     }
  562.     
  563.     for my $lang (sort keys %langs) 
  564.         {
  565.         $_ = $node;
  566.         s/(\sname\s*=\s*)"($q)"/$1"$2-$lang"/s;
  567.         s/(\s)_($w+\s*=\s*")($q)"/$1 . $2 . entity_encoded_translation($lang, $3) . '"'/seg;
  568.         print OUTPUT;
  569.         }
  570.     }
  571.  
  572.     print OUTPUT $source;
  573.  
  574.     close OUTPUT;
  575. }
  576.  
  577.  
  578. ## XML (non-bonobo-activation) merge code
  579.  
  580.  
  581. # Process tag attributes
  582. #   Only parameter is a HASH containing attributes -> values mapping
  583. sub getAttributeString
  584. {
  585.     my $sub = shift;
  586.     my $do_translate = shift || 0;
  587.     my $language = shift || "";
  588.     my $result = "";
  589.     my $translate = shift;
  590.     foreach my $e (reverse(sort(keys %{ $sub }))) {
  591.     my $key    = $e;
  592.     my $string = $sub->{$e};
  593.     my $quote = '"';
  594.     
  595.     $string =~ s/^[\s]+//;
  596.     $string =~ s/[\s]+$//;
  597.     
  598.     if ($string =~ /^'.*'$/)
  599.     {
  600.         $quote = "'";
  601.     }
  602.     $string =~ s/^['"]//g;
  603.     $string =~ s/['"]$//g;
  604.  
  605.     if ($do_translate && $key =~ /^_/) {
  606.         $key =~ s|^_||g;
  607.         if ($language) {
  608.         # Handle translation
  609.         my $decode_string = entity_decode($string);
  610.         my $translation = $translations{$language, $decode_string};
  611.         if ($translation) {
  612.             $translation = entity_encode($translation);
  613.             $string = $translation;
  614.                 }
  615.                 $$translate = 2;
  616.             } else {
  617.                  $$translate = 2 if ($translate && (!$$translate)); # watch not to "overwrite" $translate
  618.             }
  619.     }
  620.     
  621.     $result .= " $key=$quote$string$quote";
  622.     }
  623.     return $result;
  624. }
  625.  
  626. # Returns a translatable string from XML node, it works on contents of every node in XML::Parser tree
  627. sub getXMLstring
  628. {
  629.     my $ref = shift;
  630.     my $spacepreserve = shift || 0;
  631.     my @list = @{ $ref };
  632.     my $result = "";
  633.  
  634.     my $count = scalar(@list);
  635.     my $attrs = $list[0];
  636.     my $index = 1;
  637.  
  638.     $spacepreserve = 1 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?preserve["']?$/));
  639.     $spacepreserve = 0 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?default["']?$/));
  640.  
  641.     while ($index < $count) {
  642.     my $type = $list[$index];
  643.     my $content = $list[$index+1];
  644.         if (! $type ) {
  645.         # We've got CDATA
  646.         if ($content) {
  647.         # lets strip the whitespace here, and *ONLY* here
  648.                 $content =~ s/\s+/ /gs if (!$spacepreserve);
  649.         $result .= $content;
  650.         }
  651.     } elsif ( "$type" ne "1" ) {
  652.         # We've got another element
  653.         $result .= "<$type";
  654.         $result .= getAttributeString(@{$content}[0], 0); # no nested translatable elements
  655.         if ($content) {
  656.         my $subresult = getXMLstring($content, $spacepreserve);
  657.         if ($subresult) {
  658.             $result .= ">".$subresult . "</$type>";
  659.         } else {
  660.             $result .= "/>";
  661.         }
  662.         } else {
  663.         $result .= "/>";
  664.         }
  665.     }
  666.     $index += 2;
  667.     }
  668.     return $result;
  669. }
  670.  
  671. # Translate list of nodes if necessary
  672. sub translate_subnodes
  673. {
  674.     my $fh = shift;
  675.     my $content = shift;
  676.     my $language = shift || "";
  677.     my $singlelang = shift || 0;
  678.     my $spacepreserve = shift || 0;
  679.  
  680.     my @nodes = @{ $content };
  681.  
  682.     my $count = scalar(@nodes);
  683.     my $index = 0;
  684.     while ($index < $count) {
  685.         my $type = $nodes[$index];
  686.         my $rest = $nodes[$index+1];
  687.         if ($singlelang) {
  688.             my $oldMO = $MULTIPLE_OUTPUT;
  689.             $MULTIPLE_OUTPUT = 1;
  690.             traverse($fh, $type, $rest, $language, $spacepreserve);
  691.             $MULTIPLE_OUTPUT = $oldMO;
  692.         } else {
  693.             traverse($fh, $type, $rest, $language, $spacepreserve);
  694.         }
  695.         $index += 2;
  696.     }
  697. }
  698.  
  699. sub isWellFormedXmlFragment
  700. {
  701.     my $ret = eval 'require XML::Parser';
  702.     if(!$ret) {
  703.         die "You must have XML::Parser installed to run $0\n\n";
  704.     } 
  705.  
  706.     my $fragment = shift;
  707.     return 0 if (!$fragment);
  708.  
  709.     $fragment = "<root>$fragment</root>";
  710.     my $xp = new XML::Parser(Style => 'Tree');
  711.     my $tree = 0;
  712.     eval { $tree = $xp->parse($fragment); };
  713.     return $tree;
  714. }
  715.  
  716. sub traverse
  717. {
  718.     my $fh = shift; 
  719.     my $nodename = shift;
  720.     my $content = shift;
  721.     my $language = shift || "";
  722.     my $spacepreserve = shift || 0;
  723.  
  724.     if (!$nodename) {
  725.     if ($content =~ /^[\s]*$/) {
  726.         $leading_space .= $content;
  727.     }
  728.     print $fh $content;
  729.     } else {
  730.     # element
  731.     my @all = @{ $content };
  732.     my $attrs = shift @all;
  733.     my $translate = 0;
  734.     my $outattr = getAttributeString($attrs, 1, $language, \$translate);
  735.  
  736.     if ($nodename =~ /^_/) {
  737.         $translate = 1;
  738.         $nodename =~ s/^_//;
  739.     }
  740.     my $lookup = '';
  741.  
  742.         $spacepreserve = 0 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?default["']?$/));
  743.         $spacepreserve = 1 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?preserve["']?$/));
  744.  
  745.     print $fh "<$nodename", $outattr;
  746.     if ($translate) {
  747.         $lookup = getXMLstring($content, $spacepreserve);
  748.             if (!$spacepreserve) {
  749.                 $lookup =~ s/^\s+//s;
  750.                 $lookup =~ s/\s+$//s;
  751.             }
  752.  
  753.         if ($lookup || $translate == 2) {
  754.                 my $translation = $translations{$language, $lookup} if isWellFormedXmlFragment($translations{$language, $lookup});
  755.                 if ($MULTIPLE_OUTPUT && ($translation || $translate == 2)) {
  756.                     $translation = $lookup if (!$translation);
  757.                     print $fh " xml:lang=\"", $language, "\"" if $language;
  758.                     print $fh ">";
  759.                     if ($translate == 2) {
  760.                         translate_subnodes($fh, \@all, $language, 1, $spacepreserve);
  761.                     } else {
  762.                         print $fh $translation;
  763.                     }
  764.                     print $fh "</$nodename>";
  765.  
  766.                     return; # this means there will be no same translation with xml:lang="$language"...
  767.                             # if we want them both, just remove this "return"
  768.                 } else {
  769.                     print $fh ">";
  770.                     if ($translate == 2) {
  771.                         translate_subnodes($fh, \@all, $language, 1, $spacepreserve);
  772.                     } else {
  773.                         print $fh $lookup;
  774.                     }
  775.                     print $fh "</$nodename>";
  776.                 }
  777.         } else {
  778.         print $fh "/>";
  779.         }
  780.  
  781.         for my $lang (sort keys %po_files_by_lang) {
  782.                     if ($MULTIPLE_OUTPUT && $lang ne "$language") {
  783.                         next;
  784.                     }
  785.             if ($lang) {
  786.                         # Handle translation
  787.                         #
  788.                         my $translate = 0;
  789.                         my $localattrs = getAttributeString($attrs, 1, $lang, \$translate);
  790.                         my $translation = $translations{$lang, $lookup} if isWellFormedXmlFragment($translations{$lang, $lookup});
  791.                         if ($translate && !$translation) {
  792.                             $translation = $lookup;
  793.                         }
  794.  
  795.                         if ($translation || $translate) {
  796.                 print $fh "\n";
  797.                 $leading_space =~ s/.*\n//g;
  798.                 print $fh $leading_space;
  799.                  print $fh "<", $nodename, " xml:lang=\"", $lang, "\"", $localattrs, ">";
  800.                             if ($translate == 2) {
  801.                                translate_subnodes($fh, \@all, $lang, 1, $spacepreserve);
  802.                             } else {
  803.                                 print $fh $translation;
  804.                             }
  805.                             print $fh "</$nodename>";
  806.             }
  807.                     }
  808.         }
  809.  
  810.     } else {
  811.         my $count = scalar(@all);
  812.         if ($count > 0) {
  813.         print $fh ">";
  814.                 my $index = 0;
  815.                 while ($index < $count) {
  816.                     my $type = $all[$index];
  817.                     my $rest = $all[$index+1];
  818.                     traverse($fh, $type, $rest, $language, $spacepreserve);
  819.                     $index += 2;
  820.                 }
  821.         print $fh "</$nodename>";
  822.         } else {
  823.         print $fh "/>";
  824.         }
  825.     }
  826.     }
  827. }
  828.  
  829. sub intltool_tree_comment
  830. {
  831.     my $expat = shift;
  832.     my $data  = shift;
  833.     my $clist = $expat->{Curlist};
  834.     my $pos   = $#$clist;
  835.  
  836.     push @$clist, 1 => $data;
  837. }
  838.  
  839. sub intltool_tree_cdatastart
  840. {
  841.     my $expat    = shift;
  842.     my $clist = $expat->{Curlist};
  843.     my $pos   = $#$clist;
  844.  
  845.     push @$clist, 0 => $expat->original_string();
  846. }
  847.  
  848. sub intltool_tree_cdataend
  849. {
  850.     my $expat    = shift;
  851.     my $clist = $expat->{Curlist};
  852.     my $pos   = $#$clist;
  853.  
  854.     $clist->[$pos] .= $expat->original_string();
  855. }
  856.  
  857. sub intltool_tree_char
  858. {
  859.     my $expat = shift;
  860.     my $text  = shift;
  861.     my $clist = $expat->{Curlist};
  862.     my $pos   = $#$clist;
  863.  
  864.     # Use original_string so that we retain escaped entities
  865.     # in CDATA sections.
  866.     #
  867.     if ($pos > 0 and $clist->[$pos - 1] eq '0') {
  868.         $clist->[$pos] .= $expat->original_string();
  869.     } else {
  870.         push @$clist, 0 => $expat->original_string();
  871.     }
  872. }
  873.  
  874. sub intltool_tree_start
  875. {
  876.     my $expat    = shift;
  877.     my $tag      = shift;
  878.     my @origlist = ();
  879.  
  880.     # Use original_string so that we retain escaped entities
  881.     # in attribute values.  We must convert the string to an
  882.     # @origlist array to conform to the structure of the Tree
  883.     # Style.
  884.     #
  885.     my @original_array = split /\x/, $expat->original_string();
  886.     my $source         = $expat->original_string();
  887.  
  888.     # Remove leading tag.
  889.     #
  890.     $source =~ s|^\s*<\s*(\S+)||s;
  891.  
  892.     # Grab attribute key/value pairs and push onto @origlist array.
  893.     #
  894.     while ($source)
  895.     {
  896.        if ($source =~ /^\s*([\w:-]+)\s*[=]\s*["]/)
  897.        {
  898.            $source =~ s|^\s*([\w:-]+)\s*[=]\s*["]([^"]*)["]||s;
  899.            push @origlist, $1;
  900.            push @origlist, '"' . $2 . '"';
  901.        }
  902.        elsif ($source =~ /^\s*([\w:-]+)\s*[=]\s*[']/)
  903.        {
  904.            $source =~ s|^\s*([\w:-]+)\s*[=]\s*[']([^']*)[']||s;
  905.            push @origlist, $1;
  906.            push @origlist, "'" . $2 . "'";
  907.        }
  908.        else
  909.        {
  910.            last;
  911.        }
  912.     }
  913.  
  914.     my $ol = [ { @origlist } ];
  915.  
  916.     push @{ $expat->{Lists} }, $expat->{Curlist};
  917.     push @{ $expat->{Curlist} }, $tag => $ol;
  918.     $expat->{Curlist} = $ol;
  919. }
  920.  
  921. sub readXml
  922. {
  923.     my $filename = shift || return;
  924.     if(!-f $filename) {
  925.         die "ERROR Cannot find filename: $filename\n";
  926.     }
  927.  
  928.     my $ret = eval 'require XML::Parser';
  929.     if(!$ret) {
  930.         die "You must have XML::Parser installed to run $0\n\n";
  931.     } 
  932.     my $xp = new XML::Parser(Style => 'Tree');
  933.     $xp->setHandlers(Char => \&intltool_tree_char);
  934.     $xp->setHandlers(Start => \&intltool_tree_start);
  935.     $xp->setHandlers(CdataStart => \&intltool_tree_cdatastart);
  936.     $xp->setHandlers(CdataEnd => \&intltool_tree_cdataend);
  937.     my $tree = $xp->parsefile($filename);
  938.  
  939. # <foo><head id="a">Hello <em>there</em></head><bar>Howdy<ref/></bar>do</foo>
  940. # would be:
  941. # [foo, [{}, head, [{id => "a"}, 0, "Hello ",  em, [{}, 0, "there"]], bar, [{},
  942. # 0, "Howdy",  ref, [{}]], 0, "do" ] ]
  943.  
  944.     return $tree;
  945. }
  946.  
  947. sub print_header
  948. {
  949.     my $infile = shift;
  950.     my $fh = shift;
  951.     my $source;
  952.  
  953.     if(!-f $infile) {
  954.         die "ERROR Cannot find filename: $infile\n";
  955.     }
  956.  
  957.     print $fh qq{<?xml version="1.0" encoding="UTF-8"?>\n};
  958.     {
  959.         local $/;
  960.         open DOCINPUT, "<${FILE}" or die;
  961.         $source = <DOCINPUT>;
  962.         close DOCINPUT;
  963.     }
  964.     if ($source =~ /(<!DOCTYPE.*\[.*\]\s*>)/s)
  965.     {
  966.         print $fh "$1\n";
  967.     }
  968.     elsif ($source =~ /(<!DOCTYPE[^>]*>)/s)
  969.     {
  970.         print $fh "$1\n";
  971.     }
  972. }
  973.  
  974. sub parseTree
  975. {
  976.     my $fh        = shift;
  977.     my $ref       = shift;
  978.     my $language  = shift || "";
  979.  
  980.     my $name = shift @{ $ref };
  981.     my $cont = shift @{ $ref };
  982.     
  983.     while (!$name || "$name" eq "1") {
  984.         $name = shift @{ $ref };
  985.         $cont = shift @{ $ref };
  986.     }
  987.  
  988.     my $spacepreserve = 0;
  989.     my $attrs = @{$cont}[0];
  990.     $spacepreserve = 1 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?preserve["']?$/));
  991.  
  992.     traverse($fh, $name, $cont, $language, $spacepreserve);
  993. }
  994.  
  995. sub xml_merge_output
  996. {
  997.     my $source;
  998.  
  999.     if ($MULTIPLE_OUTPUT) {
  1000.         for my $lang (sort keys %po_files_by_lang) {
  1001.         if ( ! -e $lang ) {
  1002.             mkdir $lang or die "Cannot create subdirectory $lang: $!\n";
  1003.             }
  1004.             open OUTPUT, ">$lang/$OUTFILE" or die "Cannot open $lang/$OUTFILE: $!\n";
  1005.             binmode (OUTPUT) if $^O eq 'MSWin32';
  1006.             my $tree = readXml($FILE);
  1007.             print_header($FILE, \*OUTPUT);
  1008.             parseTree(\*OUTPUT, $tree, $lang);
  1009.             close OUTPUT;
  1010.             print "CREATED $lang/$OUTFILE\n" unless $QUIET_ARG;
  1011.         }
  1012.     } 
  1013.     open OUTPUT, ">$OUTFILE" or die "Cannot open $OUTFILE: $!\n";
  1014.     binmode (OUTPUT) if $^O eq 'MSWin32';
  1015.     my $tree = readXml($FILE);
  1016.     print_header($FILE, \*OUTPUT);
  1017.     parseTree(\*OUTPUT, $tree);
  1018.     close OUTPUT;
  1019.     print "CREATED $OUTFILE\n" unless $QUIET_ARG;
  1020. }
  1021.  
  1022. sub keys_merge_translations
  1023. {
  1024.     open INPUT, "<${FILE}" or die;
  1025.     open OUTPUT, ">${OUTFILE}" or die;
  1026.     binmode (OUTPUT) if $^O eq 'MSWin32';
  1027.  
  1028.     while (<INPUT>) 
  1029.     {
  1030.         if (s/^(\s*)_(\w+=(.*))/$1$2/)  
  1031.         {
  1032.         my $string = $3;
  1033.  
  1034.             print OUTPUT;
  1035.  
  1036.         my $non_translated_line = $_;
  1037.  
  1038.             for my $lang (sort keys %po_files_by_lang) 
  1039.             {
  1040.         my $translation = $translations{$lang, $string};
  1041.                 next if !$translation;
  1042.  
  1043.                 $_ = $non_translated_line;
  1044.         s/(\w+)=.*/[$lang]$1=$translation/;
  1045.                 print OUTPUT;
  1046.             }
  1047.     } 
  1048.         else 
  1049.         {
  1050.             print OUTPUT;
  1051.         }
  1052.     }
  1053.  
  1054.     close OUTPUT;
  1055.     close INPUT;
  1056. }
  1057.  
  1058. sub desktop_merge_translations
  1059. {
  1060.     open INPUT, "<${FILE}" or die;
  1061.     open OUTPUT, ">${OUTFILE}" or die;
  1062.     binmode (OUTPUT) if $^O eq 'MSWin32';
  1063.  
  1064.     while (<INPUT>) 
  1065.     {
  1066.         if (s/^(\s*)_(\w+=(.*))/$1$2/)  
  1067.         {
  1068.         my $string = $3;
  1069.  
  1070.             print OUTPUT;
  1071.  
  1072.         my $non_translated_line = $_;
  1073.  
  1074.             for my $lang (sort keys %po_files_by_lang) 
  1075.             {
  1076.                 my $translation = $translations{$lang, $string};
  1077.                 next if !$translation;
  1078.  
  1079.                 $_ = $non_translated_line;
  1080.                 s/(\w+)=.*/${1}[$lang]=$translation/;
  1081.                 print OUTPUT;
  1082.             }
  1083.     } 
  1084.         else 
  1085.         {
  1086.             print OUTPUT;
  1087.         }
  1088.     }
  1089.  
  1090.     close OUTPUT;
  1091.     close INPUT;
  1092. }
  1093.  
  1094. sub schemas_merge_translations
  1095. {
  1096.     my $source;
  1097.  
  1098.     {
  1099.        local $/; # slurp mode
  1100.        open INPUT, "<$FILE" or die "can't open $FILE: $!";
  1101.        $source = <INPUT>;
  1102.        close INPUT;
  1103.     }
  1104.  
  1105.     open OUTPUT, ">$OUTFILE" or die;
  1106.     binmode (OUTPUT) if $^O eq 'MSWin32';
  1107.  
  1108.     # FIXME: support attribute translations
  1109.  
  1110.     # Empty nodes never need translation, so unmark all of them.
  1111.     # For example, <_foo/> is just replaced by <foo/>.
  1112.     $source =~ s|<\s*_($w+)\s*/>|<$1/>|g;
  1113.  
  1114.     while ($source =~ s/
  1115.                         (.*?)
  1116.                         (\s+)(<locale\ name="C">(\s*)
  1117.                             (<default>\s*(?:<!--[^>]*?-->\s*)?(.*?)\s*<\/default>)?(\s*)
  1118.                             (<short>\s*(?:<!--[^>]*?-->\s*)?(.*?)\s*<\/short>)?(\s*)
  1119.                             (<long>\s*(?:<!--[^>]*?-->\s*)?(.*?)\s*<\/long>)?(\s*)
  1120.                         <\/locale>)
  1121.                        //sx) 
  1122.     {
  1123.         print OUTPUT $1;
  1124.  
  1125.     my $locale_start_spaces = $2 ? $2 : '';
  1126.     my $default_spaces = $4 ? $4 : '';
  1127.     my $short_spaces = $7 ? $7 : '';
  1128.     my $long_spaces = $10 ? $10 : '';
  1129.     my $locale_end_spaces = $13 ? $13 : '';
  1130.     my $c_default_block = $3 ? $3 : '';
  1131.     my $default_string = $6 ? $6 : '';
  1132.     my $short_string = $9 ? $9 : '';
  1133.     my $long_string = $12 ? $12 : '';
  1134.  
  1135.     print OUTPUT "$locale_start_spaces$c_default_block";
  1136.  
  1137.         $default_string =~ s/\s+/ /g;
  1138.         $default_string = entity_decode($default_string);
  1139.     $short_string =~ s/\s+/ /g;
  1140.     $short_string = entity_decode($short_string);
  1141.     $long_string =~ s/\s+/ /g;
  1142.     $long_string = entity_decode($long_string);
  1143.  
  1144.     for my $lang (sort keys %po_files_by_lang) 
  1145.         {
  1146.         my $default_translation = $translations{$lang, $default_string};
  1147.         my $short_translation = $translations{$lang, $short_string};
  1148.         my $long_translation  = $translations{$lang, $long_string};
  1149.  
  1150.         next if (!$default_translation && !$short_translation && 
  1151.                      !$long_translation);
  1152.  
  1153.         print OUTPUT "\n$locale_start_spaces<locale name=\"$lang\">";
  1154.  
  1155.         print OUTPUT "$default_spaces";    
  1156.  
  1157.         if ($default_translation)
  1158.         {
  1159.             $default_translation = entity_encode($default_translation);
  1160.             print OUTPUT "<default>$default_translation</default>";
  1161.         }
  1162.  
  1163.         print OUTPUT "$short_spaces";
  1164.  
  1165.         if ($short_translation)
  1166.         {
  1167.             $short_translation = entity_encode($short_translation);
  1168.             print OUTPUT "<short>$short_translation</short>";
  1169.         }
  1170.  
  1171.         print OUTPUT "$long_spaces";
  1172.  
  1173.         if ($long_translation)
  1174.         {
  1175.             $long_translation = entity_encode($long_translation);
  1176.             print OUTPUT "<long>$long_translation</long>";
  1177.         }        
  1178.  
  1179.         print OUTPUT "$locale_end_spaces</locale>";
  1180.         }
  1181.     }
  1182.  
  1183.     print OUTPUT $source;
  1184.  
  1185.     close OUTPUT;
  1186. }
  1187.  
  1188. sub rfc822deb_merge_translations
  1189. {
  1190.     my %encodings = ();
  1191.     for my $lang (keys %po_files_by_lang) {
  1192.         $encodings{$lang} = ($UTF8_ARG ? 'UTF-8' : get_po_encoding($po_files_by_lang{$lang}));
  1193.     }
  1194.  
  1195.     my $source;
  1196.  
  1197.     $Text::Wrap::huge = 'overflow';
  1198.     $Text::Wrap::break = qr/\n|\s(?=\S)/;
  1199.  
  1200.     {
  1201.        local $/; # slurp mode
  1202.        open INPUT, "<$FILE" or die "can't open $FILE: $!";
  1203.        $source = <INPUT>;
  1204.        close INPUT;
  1205.     }
  1206.  
  1207.     open OUTPUT, ">${OUTFILE}" or die;
  1208.     binmode (OUTPUT) if $^O eq 'MSWin32';
  1209.  
  1210.     while ($source =~ /(^|\n+)(_*)([^:\s]+)(:[ \t]*)(.*?)(?=\n[\S\n]|$)/sg)
  1211.     {
  1212.         my $sep = $1;
  1213.         my $non_translated_line = $3.$4;
  1214.         my $string = $5;
  1215.         my $underscore = length($2);
  1216.         next if $underscore eq 0 && $non_translated_line =~ /^#/;
  1217.         #  Remove [] dummy strings
  1218.         my $stripped = $string;
  1219.         $stripped =~ s/\[\s[^\[\]]*\],/,/g if $underscore eq 2;
  1220.         $stripped =~ s/\[\s[^\[\]]*\]$//;
  1221.         $non_translated_line .= $stripped;
  1222.  
  1223.         print OUTPUT $sep.$non_translated_line;
  1224.     
  1225.         if ($underscore) 
  1226.         {
  1227.             my @str_list = rfc822deb_split($underscore, $string);
  1228.  
  1229.             for my $lang (sort keys %po_files_by_lang) 
  1230.                 {
  1231.                     my $is_translated = 1;
  1232.                     my $str_translated = '';
  1233.                     my $first = 1;
  1234.                 
  1235.                     for my $str (@str_list) 
  1236.                     {
  1237.                         my $translation = $translations{$lang, $str};
  1238.                     
  1239.                         if (!$translation) 
  1240.                         {
  1241.                             $is_translated = 0;
  1242.                             last;
  1243.                         }
  1244.  
  1245.                     #  $translation may also contain [] dummy
  1246.                         #  strings, mostly to indicate an empty string
  1247.                     $translation =~ s/\[\s[^\[\]]*\]$//;
  1248.                         
  1249.                         if ($first) 
  1250.                         {
  1251.                             if ($underscore eq 2)
  1252.                             {
  1253.                                 $str_translated .= $translation;
  1254.                             }
  1255.                             else
  1256.                             {
  1257.                                 $str_translated .=
  1258.                                     Text::Tabs::expand($translation) .
  1259.                                     "\n";
  1260.                             }
  1261.                         } 
  1262.                         else 
  1263.                         {
  1264.                             if ($underscore eq 2)
  1265.                             {
  1266.                                 $str_translated .= ', ' . $translation;
  1267.                             }
  1268.                             else
  1269.                             {
  1270.                                 $str_translated .= Text::Tabs::expand(
  1271.                                     Text::Wrap::wrap(' ', ' ', $translation)) .
  1272.                                     "\n .\n";
  1273.                             }
  1274.                         }
  1275.                         $first = 0;
  1276.  
  1277.                         #  To fix some problems with Text::Wrap::wrap
  1278.                         $str_translated =~ s/(\n )+\n/\n .\n/g;
  1279.                     }
  1280.                     next unless $is_translated;
  1281.  
  1282.                     $str_translated =~ s/\n \.\n$//;
  1283.                     $str_translated =~ s/\s+$//;
  1284.  
  1285.                     $_ = $non_translated_line;
  1286.                     s/^(\w+):\s*.*/$sep${1}-$lang.$encodings{$lang}: $str_translated/s;
  1287.                     print OUTPUT;
  1288.                 }
  1289.         }
  1290.     }
  1291.     print OUTPUT "\n";
  1292.  
  1293.     close OUTPUT;
  1294.     close INPUT;
  1295. }
  1296.  
  1297. sub rfc822deb_split 
  1298. {
  1299.     # Debian defines a special way to deal with rfc822-style files:
  1300.     # when a value contain newlines, it consists of
  1301.     #   1.  a short form (first line)
  1302.     #   2.  a long description, all lines begin with a space,
  1303.     #       and paragraphs are separated by a single dot on a line
  1304.     # This routine returns an array of all paragraphs, and reformat
  1305.     # them.
  1306.     # When first argument is 2, the string is a comma separated list of
  1307.     # values.
  1308.     my $type = shift;
  1309.     my $text = shift;
  1310.     $text =~ s/^[ \t]//mg;
  1311.     return (split(/, */, $text, 0)) if $type ne 1;
  1312.     return ($text) if $text !~ /\n/;
  1313.  
  1314.     $text =~ s/([^\n]*)\n//;
  1315.     my @list = ($1);
  1316.     my $str = '';
  1317.  
  1318.     for my $line (split (/\n/, $text)) 
  1319.     {
  1320.         chomp $line;
  1321.         if ($line =~ /^\.\s*$/)
  1322.         {
  1323.             #  New paragraph
  1324.             $str =~ s/\s*$//;
  1325.             push(@list, $str);
  1326.             $str = '';
  1327.         } 
  1328.         elsif ($line =~ /^\s/) 
  1329.         {
  1330.             #  Line which must not be reformatted
  1331.             $str .= "\n" if length ($str) && $str !~ /\n$/;
  1332.             $line =~ s/\s+$//;
  1333.             $str .= $line."\n";
  1334.         } 
  1335.         else 
  1336.         {
  1337.             #  Continuation line, remove newline
  1338.             $str .= " " if length ($str) && $str !~ /\n$/;
  1339.             $str .= $line;
  1340.         }
  1341.     }
  1342.  
  1343.     $str =~ s/\s*$//;
  1344.     push(@list, $str) if length ($str);
  1345.  
  1346.     return @list;
  1347. }
  1348.  
  1349.